#ifndef __M_SINK_H__
#define __M_SINK_H__
/* 日志落地模块的实现
    1.抽象落地模块类
    2.派生子类（根据不同的落地方向派生）
    3.使用工厂模式进行创建与表示的分离
*/
#include "util.hpp"
#include <memory>
#include <fstream>
#include <sstream>
#include <cassert>
#include <unistd.h>

namespace log {
    // 抽象基类
    class LogSink {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        // 可能有的落地方向涉及到资源的释放，所以需要析构函数能构成多态，能够让基类指针或引用统一调用
        virtual ~LogSink() {};
        // 不同落地方向重写本接口，实现落地方向的区别
        virtual void log(const char *data, size_t length) {}
    };
    // 标准输出流落地
    class StdoutLogSink : public LogSink {
    public:
        virtual void log(const char *data, size_t length) override {
            // 这里不使用 << 因为operator<<()无法指定输出数据的大小，而是直到'\0'截止
            // 但是data不一定是以'\0'截止的，所以为了安全性，输出指定大小的数据更可控
            std::cout.write(data, length);
        }
    };
    // 单文件落地
    class FileLogSink : public LogSink {
    public:
        FileLogSink(const std::string &path_name) 
            :path_name_(path_name) {
            // 1.创建日志所在的目录
            util::File::create_directory(util::File::get_path(path_name));
            // 2.打开日志文件，（以写方式创建文件时，不存在会自动创建）
            // ofstream默认以写创建文件，日志信息需要追加到文件末尾，需要append选项
            ofs_.open(path_name_, std::ios::binary | std::ios::app);
            assert(ofs_.is_open());
        }
        ~FileLogSink() override {
            ofs_.close();
        }
        virtual void log(const char *data, size_t length) override {
            ofs_.write(data, length);
            assert(ofs_.good());// 检查是否有错误
        }
    private:
        std::string path_name_;
        std::ofstream ofs_;
    };
    // 滚动文件落地 以文件大小为依据进行
    class RollBySizeLogSink : public LogSink {
    public:
        // 构造时传入文件名，打开文件并保存文件句柄到ofs_
        RollBySizeLogSink(const std::string &base_name, size_t max_fsize)
            : base_name_(base_name),
            max_fsize_(max_fsize),
            cur_fsize_(0) {
            std::string path_name = create_new_file();
            // 1.创建日志所在的目录
            util::File::create_directory(util::File::get_path(path_name));
            // 2.打开日志文件，（以写方式创建文件时，不存在会自动创建）
            // ofstream默认以写创建文件，日志信息需要追加到文件末尾，需要append选项
            ofs_.open(path_name, std::ios::binary | std::ios::app);
            assert(ofs_.is_open());
        }
        // 将日志消息写入文件--写入前判断文件大小，超过了最大大小就要执行切换文件操作再写入
        virtual void log(const char *data, size_t length) override {
            // 超过文件最大大小就需要关闭当前日志文件，创建并打开新的日志文件了
            if (cur_fsize_ >= max_fsize_) {
                cur_fsize_ = 0;
                ofs_.close();
                std::string path_name = create_new_file();
                ofs_.open(path_name, std::ios::binary | std::ios::app);
                assert(ofs_.is_open());// 检查是否有错误
            }
            ofs_.write(data, length);
            assert(ofs_.good());
            cur_fsize_ += length;
        }
    private:
        // 进行大小判断，超过指定大小则创建新文件
        std::string create_new_file() {
            // 获取系统当前时间戳
            time_t t = util::Date::get_time();
            // 转换为时间结构体
            struct tm tt;
            localtime_r(&t, &tt);
            // 拼接为完整文件名
            std::stringstream file_name;
            file_name << base_name_;
            file_name << tt.tm_year + 1900;
            file_name << tt.tm_mon + 1;
            file_name << tt.tm_mday;
            file_name << tt.tm_hour;
            file_name << tt.tm_min;
            file_name << tt.tm_sec;
            file_name << "-";
            file_name << name_count_++;
            file_name << ".log";
            //返回
            return file_name.str();
        }
    private:
        //当前输出到的文件名 = 基础文件名 + 扩展文件名（比如用时间）
        std::string base_name_;// ./logs/base-  ->  ./logs/base-20240923122245.log 
        std::ofstream ofs_; // 保存文件句柄
        size_t max_fsize_;// 一个文件最大的大小
        size_t cur_fsize_;// 当前文件的大小
        size_t name_count_;// 文件编号，从0递增，防止在1s内创建多个文件时导致的文件名重复
    };


    // 工厂类  管理
    /* 简单工厂模式
        1.create需要设计一个参数，根据外界传入参数数值的不同创建不同的落地日志派生类
        2.坏处是破坏了类的封装，违反了开闭原则，如果新增了落地方向时，工厂类需要新增对应的代码
        class SinkFactory {
        public:
            static LogSink::ptr create(int value) {
                switch(value) {
                    case 1 : return std::make_shared<StdoutLogSink>(std::forward<Args>(args)...); break;
                    case 2 : return std::make_shared<FileLogSink>(std::forward<Args>(args)...); break;
                    case 3 : return std::make_shared<RollBySizeLogSink>(std::forward<Args>(args)...); break;
                    default : return std::make_shared<LogSink>(std::forward<Args>(args)...); break;
                }
            }
        }
    */
    /* 函数模版 + 可变参数
        1.工厂类create函数接受一个类型，返回该类型的智能指针对象，这解决了新增落地方向派生类时需要修改工厂类代码的问题
        2.不同落地方向派生类的构造函数的参数可能不同，有的没有参数、有的有1个参数、有的有多个参数，所以
            直接固定工厂类构造函数的参数不可取。使用可变参数，并在适当的地方展开即可解决。
            
        为什么不整成模版类+不定参?
        因为不定参是针对函数模版的，类没有不定参，类模版设置了不定参也传递不到create函数
    */
    // template <typename SinkType, typename ...Args>
    // template <typename SinkType>
    class SinkFactory {
    public:
        // template <typename ...Args>
        template <typename SinkType, typename ...Args>
        static LogSink::ptr create(Args &&...args) {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif